#include "libdjvu/ddjvuapi.h"
#include "napi/native_api.h"
#include "promise/base.h"
#include "promise/page_size.h"
#include "promise/open.h"
#include "promise/page_info.h"
#include "promise/release.h"
#include <cstring>
#include <exception>
#include <stdint.h>

static napi_value Open(napi_env env, napi_callback_info info) {
    napi_value result;
    try {
        size_t argc = 2;
        napi_value args[2] = {nullptr};
        napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
        char name[256];
        size_t name_len;
        napi_get_value_string_utf8(env, args[0], name, sizeof(name), &name_len);
        char path[256];
        size_t path_length;
        napi_get_value_string_utf8(env, args[1], path, sizeof(path), &path_length);
        instance = ddjvu_context_create(name);
        int success = 0;
        if (instance != NULL) {
            ddjvu_cache_set_size(instance, CACHE_SIZE);
            document = ddjvu_document_create_by_filename_utf8(instance, path, 1);
            if (document == NULL) {
                ddjvu_context_release(instance);
            } else {
                success = 1;
            }
        }
        napi_create_int32(env, success, &result);
    } catch (std::exception &e) {
        napi_create_int32(env, 0, &result);
    }
    return result;
}

static napi_value OpenAsync(napi_env env, napi_callback_info info) {
    size_t argc = 2;
    napi_value args[2];
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    napi_value promise = nullptr;
    napi_deferred deferred = nullptr;
    napi_create_promise(env, &deferred, &promise);
    auto callbackData = new OpenCallbackData();
    callbackData->deferred = deferred;
    char name[256];
    size_t name_len;
    napi_get_value_string_utf8(env, args[0], name, sizeof(name), &name_len);
    callbackData->name = new char[256];
    strncpy(callbackData->name, name, 255);
    callbackData->name[255] = '\0';
    char path[256];
    size_t path_len;
    napi_get_value_string_utf8(env, args[1], path, sizeof(path), &path_len);
    callbackData->path = new char[256];
    strncpy(callbackData->path, path, 255);
    callbackData->path[255] = '\0';
    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, "OpenAsync", NAPI_AUTO_LENGTH, &resourceName);
    napi_create_async_work(env, nullptr, resourceName, OpenExecute, OpenComplete, callbackData,
                           &callbackData->asyncWork);
    napi_queue_async_work(env, callbackData->asyncWork);
    return promise;
}

static napi_value Release(napi_env env, napi_callback_info info) {
    try {
        if (document != NULL) {
            ddjvu_job_release(ddjvu_document_job(document));
        }
        ddjvu_context_release(instance);
    } catch (std::exception &e) {
    }
    return nullptr;
}

static napi_value ReleaseAsync(napi_env env, napi_callback_info info) {
    napi_value promise = nullptr;
    napi_deferred deferred = nullptr;
    napi_create_promise(env, &deferred, &promise);
    auto callbackData = new ReleaseCallbackData();
    callbackData->deferred = deferred;
    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, "ReleaseAsync", NAPI_AUTO_LENGTH, &resourceName);
    napi_create_async_work(env, nullptr, resourceName, ReleaseExecute, ReleaseComplete, callbackData,
                           &callbackData->asyncWork);
    napi_queue_async_work(env, callbackData->asyncWork);
    return promise;
}

static napi_value PageSize(napi_env env, napi_callback_info info) {
        napi_value result;
    try {
        int num = ddjvu_document_get_pagenum(document);
        napi_create_int32(env, num, &result);
    } catch (std::exception &e) {
    }
    return result;
}

static napi_value PageSizeAsync(napi_env env, napi_callback_info info) {
    napi_value promise = nullptr;
    napi_deferred deferred = nullptr;
    napi_create_promise(env, &deferred, &promise);
    auto callbackData = new MetadataCallbackData();
    callbackData->deferred = deferred;
    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, "PageSizeAsync", NAPI_AUTO_LENGTH, &resourceName);
    napi_create_async_work(env, nullptr, resourceName, PageSizeExecute, PageSizeComplete, callbackData,
                           &callbackData->asyncWork);
    napi_queue_async_work(env, callbackData->asyncWork);
    return promise;
}

static napi_value PageInfo(napi_env env, napi_callback_info info) {
    napi_value result;
    napi_create_object(env, &result);
    try {
        size_t argc = 3;
        napi_value args[3] = {nullptr};
        napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
        uint32_t index;
        napi_get_value_uint32(env, args[0], &index);
        uint32_t scale;
        napi_get_value_uint32(env, args[1], &scale);
        uint32_t samples;
        napi_get_value_uint32(env, args[2], &samples);
        ddjvu_page_t *page = ddjvu_page_create_by_pageno(document, index);
        if (page != NULL) {
            ddjvu_job_t *job = ddjvu_page_job(page);
            while (ddjvu_job_status(job) < DDJVU_JOB_OK) {
                handle_message();
            }
            if (ddjvu_job_status(job) < DDJVU_JOB_FAILED) {
                int width = ddjvu_page_get_width(page);
                napi_value width_key;
                napi_value width_value;
                napi_create_string_utf8(env, "width", NAPI_AUTO_LENGTH, &width_key);
                napi_create_int32(env, width, &width_value);
                napi_set_property(env, result, width_key, width_value);
                int height = ddjvu_page_get_height(page);
                napi_value height_key;
                napi_value height_value;
                napi_create_string_utf8(env, "height", NAPI_AUTO_LENGTH, &height_key);
                napi_create_int32(env, height, &height_value);
                napi_set_property(env, result, height_key, height_value);
                int dpi = ddjvu_page_get_resolution(page);
                napi_value dpi_key;
                napi_value dpi_value;
                napi_create_string_utf8(env, "dpi", NAPI_AUTO_LENGTH, &dpi_key);
                napi_create_int32(env, dpi, &dpi_value);
                napi_set_property(env, result, dpi_key, dpi_value);
                ddjvu_format_style_t style = samples == 1 ? DDJVU_FORMAT_GREY8 : DDJVU_FORMAT_RGB24;
                ddjvu_format_t *fmt = ddjvu_format_create(style, 0, NULL);
                if (fmt != NULL) {
                    ddjvu_format_set_row_order(fmt, 1);
                    ddjvu_format_set_y_direction(fmt, 1);
                    ddjvu_rect_t rect;
                    rect.x = 0;
                    rect.y = 0;
                    rect.w = scale * width;
                    rect.h = scale * height;
                    int row_size = samples * rect.w;
                    int data_len = row_size * rect.h;
                    char *buffer = new char[data_len];
                    ddjvu_page_render(page, DDJVU_RENDER_COLOR, &rect, &rect, fmt, row_size, buffer);
                    ddjvu_job_t *job = ddjvu_page_job(page);
                    while (ddjvu_job_status(job) < DDJVU_JOB_OK) {
                        handle_message();
                    }
                    ddjvu_format_release(fmt);
                    if (ddjvu_job_status(job) < DDJVU_JOB_FAILED) {
                        void *buffer_ptr = nullptr;
                        napi_value array_buffer;
                        napi_create_arraybuffer(env, data_len, &buffer_ptr, &array_buffer);
                        memcpy(buffer_ptr, buffer, data_len);
                        napi_value pixel_key;
                        napi_create_string_utf8(env, "pixel", NAPI_AUTO_LENGTH, &pixel_key);
                        napi_set_property(env, result, pixel_key, array_buffer);
                    }
                }
            }
        }
    } catch (std::exception &e) {
    }
    return result;
}

static napi_value PageInfoAsync(napi_env env, napi_callback_info info) {
    size_t argc = 3;
    napi_value args[3];
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    napi_value promise = nullptr;
    napi_deferred deferred = nullptr;
    napi_create_promise(env, &deferred, &promise);
    auto callbackData = new PageInfoCallbackData();
    callbackData->deferred = deferred;
    napi_get_value_uint32(env, args[0], &callbackData->in_index);
    napi_get_value_uint32(env, args[1], &callbackData->in_scale);
    napi_get_value_uint32(env, args[2], &callbackData->in_samples);
    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, "PageInfoAsync", NAPI_AUTO_LENGTH, &resourceName);
    napi_create_async_work(env, nullptr, resourceName, PageInfoExecute, PageInfoComplete, callbackData,
                           &callbackData->asyncWork);
    napi_queue_async_work(env, callbackData->asyncWork);
    return promise;
}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
    napi_property_descriptor desc[] = {
        {"open", nullptr, Open, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"openAsync", nullptr, OpenAsync, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"release", nullptr, Release, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"releaseAsync", nullptr, ReleaseAsync, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"pageSize", nullptr, PageSize, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"pageSizeAsync", nullptr, PageSizeAsync, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"pageInfo", nullptr, PageInfo, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"pageInfoAsync", nullptr, PageInfoAsync, nullptr, nullptr, nullptr, napi_default, nullptr}};
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "djvulibre",
    .nm_priv = ((void *)0),
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterDjvulibreModule(void) { napi_module_register(&demoModule); }
